#include <fdc.h>
#include <std.h>
#include <pio.h>
#include <error.h>
#include <dint.h>
#include <biosdisk.h>
#include <memory.h>
#include <irq.h>
#include <timer.h>
#include <diskfmt.h>
#include <vol.h>


#define u32 unsigned long
#define u16 unsigned short
#define u8 unsigned char

#define MAXREADLEN 0x80000-0x1000


//FAT32 code-------------------------------------------------------------------------------------:
/*All code below is for fat32 unless specified in the function name.*/

#define EXTPART 0x0f

static struct BPB_struct *BPBptr;
static struct BPB_struct_FAT32 *BPBptr_fat32;
static struct FSI_struct *FSIptr;

static struct DIR_struct DIR,*DIR1;
static struct LDIR_struct LDIR,*LDIRptr;

static struct BPB_struct BPB;
static struct BPB_struct_FAT32 BPB_fat32;
static u32 start_lba;
static u32 current_cluster;
#define sFSI_LeadSig 0x41615252
#define sFSI_StrucSig 0x61417272
#define sFSI_Free_Count_unknown 0xffffffff
#define sFSI_TrailSig 0xAA550000

#define ATTR_READ_ONLY 	0x01
#define ATTR_HIDDEN 	0x02
#define ATTR_SYSTEM 	0x04
#define ATTR_VOLUME_ID 	0x08
#define ATTR_DIRECTORY 	0x10
#define ATTR_ARCHIVE 	0x20
#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)
#define ATTR_LONG_NAME_MASK ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE



#define sBPB_media 0xf8

/*Root dir sectors for FAT32 is zero*/
#define FATSz (BPB_fat32.FATSz32)
#define FirstDataSector (BPB.ResvdSecCnt + (BPB.NumFATs * FATSz) + 0/*Root dir sectors=0*/)
#define FirstSectorofCluster(N) (((N - 2) * BPB.SecPerClus) + FirstDataSector)
#define FATOffset(N) (N*4)
#define ThisFATSecNum(N) (BPB.ResvdSecCnt + (FATOffset(N) / BPB.BytsPerSec))
#define ThisFATEntOffset(N) (FATOffset(N) % BPB.BytsPerSec)


void read_bpb(disk_volume *vol)
{
	char *buf;
	printk("\nReading BPB of volume at %d",vol->start_lba);
	buf=(char *)get_mem(512);
	read_drive(buf,vol->start_lba,1);
	BPBptr=(struct BPB_struct *)buf;
	BPBptr_fat32=(struct BPB_struct_FAT32 *)(buf+36);	//FAT32 type specific structure.
	copy(&BPB,BPBptr,struct BPB_struct);
	copy(&BPB_fat32,BPBptr_fat32,struct BPB_struct_FAT32);
	copy(&vol->BPB,BPBptr,struct BPB_struct);
	copy(&vol->BPB_fat32,BPBptr_fat32,struct BPB_struct_FAT32);
	/*The BPB now exists both in the caller's structures and our local structure*/
	free(buf);
}


/*Detect filesystem*/
int read_fsinfo(disk_volume *vol)
{
	char *buf;
	printk("\nReading FileSystem Info:");
	buf=(char *)get_mem(512);
	read_drive(buf,vol->start_lba+vol->BPB_fat32.FSInfo,1);
	FSIptr=(struct FSI_struct *)buf;
	copy(&vol->FSI,FSIptr,struct FSI_struct);
	if(vol->FSI.LeadSig==sFSI_LeadSig && vol->FSI.StrucSig==sFSI_StrucSig && vol->FSI.TrailSig==sFSI_TrailSig)	{
		printk("\nVerified FAT");
		return SUCCESS;
	}
	else	{
		error("\nFAT signature errors");
		return FAILED;
	}
	free(buf);
}

static u8 *SecBuff;
u32 read_cluster_entry(u32 cluster)
{
	u32 entry;
	u32 *e;
	read_drive(SecBuff,start_lba+ThisFATSecNum(cluster),1);	/*start_lba was set before calling this function to the starting sector address of the volume we are currently operating on*/
	e=(u32 *)(SecBuff+ThisFATEntOffset(cluster));
	entry=(*e)&0x0fffffff;
	return entry;
}

int legal_name(struct DIR_struct *test)
{
	int i;
	for(i=0;i<11;i++)	{
		if(test->Name[i]<0x20 || test->Name[i]==0x22 || test->Name[i]==0x2a || test->Name[i]==0x2b || test->Name[i]==0x2c || test->Name[i]==0x2e || test->Name[i]==0x2f || test->Name[i]==0x3a || test->Name[i]==0x3b || test->Name[i]==0x3c || test->Name[i]==0x3d || test->Name[i]==0x3e || test->Name[i]==0x3f || test->Name[i]==0x5b || test->Name[i]==0x5c || test->Name[i]==0x5d || test->Name[i]==0x7c)
			return NULL;
	}
	return TRUE;
}

int read_cluster(u8 *cluster_buffer,u32 cluster)
{
	return read_drive(cluster_buffer,start_lba+FirstSectorofCluster(cluster),BPB.SecPerClus);;
}


int get_name(char *name,struct DIR_struct *dir)
{
	int s,i=0,j=0;
	for(s=0;s<8;s++)	{
		if(dir->Name[s]==0x20)
			break;
		name[s]=dir->Name[s];
	}
	if((dir->Attr)&ATTR_DIRECTORY)	{
		name[s]=NULL;
		return NULL;
	}
	name[s++]='.';
	for(i=0;i<3;i++)	{
		if(dir->Name[8+i]!=0x20)	{
			name[s+j]=dir->Name[8+i];
			j++;
		}
	}
	name[s+j]=NULL;
	return NULL;
}

#define file_cluster(a,b) (b*0x10000+a)
/*This function calculates the starting cluster of a directory entry*/
u32 get_cluster(struct DIR_struct *dir)
{
	return file_cluster(dir->FstClusLO,dir->FstClusHI);
}


/*Reads a file into the buffer 'ret'. The buffer needs to be allocated and
the file needs to be a file or else you'll be reading the directory (and you'll see file entries)*/
int read_file(u8 *ret,struct DIR_struct *dir)
{
	u8 *file_buffer=ret;
	u32 clus,n_clus;
	n_clus=dir->FileSize/((BPB.SecPerClus)*(BPB.BytsPerSec))+1;
	sane(!ret,"Read file buf is null");
	printk("\nReading file at cluster:%d",get_cluster(dir));
	clus=get_cluster(dir);
	while(clus<0x0ffffff8 && --n_clus>=0)	{
		read_cluster(file_buffer,clus);
		file_buffer+=(BPB.BytsPerSec)*(BPB.SecPerClus);
		clus=read_cluster_entry(clus);
	}
	return SUCCESS;
}

/*Checks for deleted/invalid/LFN entries*/
/*LFN is the Long file name extension of FAT32. In earlier FATs a name of only 8.3 (11)
characters was supported. To extend it but maintain backward compatibility names
are stored using multiple such entries called LFN/LD entries.*/
int valid_entry(struct DIR_struct *test)
{
	if(test->Name[0]==0xe5 || test->Name[0]==0 || test->Attr==ATTR_LONG_NAME)
		return NULL;
	return TRUE;
}

/*Function to search filename in the directory of the given cluster and 
put the directory structure in global 'DIR'*/

int get_entry(u32 cluster,char *filename)
{
	u32 nclus=0,clus=cluster,alloc,i=0;
	u8 *clus_buffer;
	char name[11];
	printk("\nFinding %s in cluster #%d",filename,cluster);
	while(clus<0x0ffffff8)	{
		nclus++;
		clus=read_cluster_entry(clus);
	}
	alloc=(BPB.BytsPerSec)*(BPB.SecPerClus)*nclus;
	clus_buffer=(u8 *)get_mem(alloc);
	clus=cluster;
	while(clus<0x0ffffff8)	{
		read_cluster(clus_buffer,clus);
		clus=read_cluster_entry(clus);
	}
	while(i<(alloc/32))	{
		DIR1=(struct DIR_struct *)(clus_buffer+i*32);
		copy(&DIR,DIR1,struct DIR_struct);
		get_name(name,&DIR);
		if(valid_entry(&DIR))	{
			if(strcmp(name,filename)==NULL)	{
				free(clus_buffer);
				return SUCCESS;
			}
		}
		i++;
	}
	free(clus_buffer);
	return FAILED;		
}

/*Lists the entries of the directory pointed to by the current_cluster entry in the
disk_volume structure*/
int list_entries()
{
	u32 nclus=0,clus=current_cluster,alloc,i=0;
	u8 *clus_buffer;
	char name[11];
	printk("\nListing entries at cluster #%d",clus);
	while(clus<0x0ffffff8)	{
		nclus++;
		clus=read_cluster_entry(clus);
	}
	alloc=(BPB.BytsPerSec)*(BPB.SecPerClus)*nclus;
	clus_buffer=(u8 *)get_mem(alloc);
	clus=current_cluster;
	while(clus<0x0ffffff8)	{
		read_cluster(clus_buffer,clus);
		clus=read_cluster_entry(clus);
	}
	while(i<(alloc/32))	{
		DIR1=(struct DIR_struct *)(clus_buffer+i*32);
		copy(&DIR,DIR1,struct DIR_struct);
		get_name(name,&DIR);
		if(valid_entry(&DIR))	{
			printk("\n%s",name);
		}
		i++;
	}
	free(clus_buffer);
	return SUCCESS;
}
kdrv diskfmt;

u8 *go_entry(char *name)
{
	u8 *buf=NULL;
	if(get_entry(current_cluster,name)==NULL)	{
		if(DIR.Attr & ATTR_DIRECTORY)	{
			current_cluster=get_cluster(&DIR);
			list_entries();
		} else	{
			buf=(u8 *)get_mem(DIR.FileSize);
			read_file(buf,&DIR);
		}
	} else	{
		device_error(&diskfmt,"No such entry");
		printk(name);
	}
	return buf;
}

/*This function is used to check for a folder/file's presence in the current directory.*/
int query_current_entry(char *name)
{
	if(get_entry(current_cluster,name)==NULL)	{
		return SUCCESS;
	} else	{
		printk("\nNo such entry:%s",name);
		return FAILED;
	}
}

/*This function is used to detect the presence of a directory/file in the file system*/
int query_open_(char *name)
{
	int i=1;
	char *opt;
	current_cluster=BPB_fat32.RootClus;
	opt=(char *)get_mem(256);
	printk("\nQuerying:%s in filesystem",name);
	if(*name!='/')
		return FAILED;
	getstringparameter(name,opt,'/',i);
	if(strlen(opt)==NULL)	{
		list_entries();
		return SUCCESS;	/*Root directory is always present (provided we have detected the file system)*/
	}
	while(strlen(opt)!=NULL)	{
		if(query_current_entry(opt)==FAILED)	{
			return FAILED;
		}
		getstringparameter(name,opt,'/',++i);
	}
	return SUCCESS;
}

//File string parser and eye opener
u8 *open_(char *name)
{
	int i=1;
	char *opt;
	u8 *buf=NULL;
	current_cluster=BPB_fat32.RootClus;
	opt=(char *)get_mem(256);
	printk("\nOpen:%s",name);
	if(*name!='/')
		return NULL;
	getstringparameter(name,opt,'/',i);
	if(strlen(opt)==NULL)	{
		list_entries();
		return NULL;
	}
	while(strlen(opt)!=NULL)	{
		buf=go_entry(opt);
		getstringparameter(name,opt,'/',++i);
	}
	return buf;
}

int set_volume(disk_volume *vol)
{
	copy(&BPB,&vol->BPB,struct BPB_struct);
	copy(&BPB_fat32,&vol->BPB_fat32,struct BPB_struct_FAT32);
	current_cluster=vol->current_cluster;
	start_lba=vol->start_lba;
	printk("\nSet volume for fs driver to:%d",start_lba);
	return SUCCESS;
}

char *deperr="diskfmt:driver dependency error";
int init_diskfmt()
{
	printk("\nReading disk structures..");
	SecBuff=(u8 *)get_mem(512);
	return SUCCESS;
}

int deinit_diskfmt()
{
	if(SecBuff!=NULL)
		free(SecBuff);
	return SUCCESS;
}
